#define CAFF_VERSION "1.1.3"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdarg.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <stdbool.h>
#include "ae.h"
#include "caff.h"

#define SUCCESS		1
#define FAIL		2

void trim(char *c);

void initServer(void);
void daemonize();
void savepid(const pid_t pid, const char *pid_file);
void run(void);
void sock_accept(aeEventLoop *el, int fd, void *privdata, int mask);
void sock_read(aeEventLoop *el, int fd, void *privdata, int mask);
void sock_write(aeEventLoop *el, int fd, void *privdata, int mask);
void setnonblock(int fd);
caffClient *clientCreate(int fd);
void clientFree(caffClient *c);
int runCommand(caffClient *c);
void sendCli(caffClient *c, char *msg);
void getline(char *str, char *line);
int getlinenum(char *str);

void inituuid(caffClient *c, uuidtype start);
void uuid(caffClient *c);


int tokenize(char *str, char **tokens, char token, int maxtoken);

void initServer(){
	server.uuidnum = NULL;
	server.el = aeCreateEventLoop();
	run();
}

void run(){
    int fd,opt=1;
    struct sockaddr_in addr;
    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        exit(1);
    }

	setnonblock(fd);
	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(server.port);
    addr.sin_addr.s_addr = 0;
    if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1)
    {
        exit(1);
    }
 
    if (listen(fd, 1024) == -1)
    {
        exit(1);
    }

	if(aeCreateFileEvent(server.el,fd,AE_READABLE,sock_accept,NULL) == AE_ERR)
	{
		exit(1);
	}
	aeMain(server.el);
}

void sock_accept(aeEventLoop *el, int fd, void *privdata, int mask){
	int s;

	struct sockaddr addr;
	socklen_t len = sizeof(addr);

	if((s = accept(fd, &addr, &len)) == -1){
		return;
	}
	caffClient *c = clientCreate(s);
}

void sock_write(aeEventLoop *el, int fd, void *privdata, int mask){
	aeDeleteFileEvent(server.el,fd,AE_WRITABLE);
	caffClient *c = privdata;
	send(c->fd, c->retbuf, strlen(c->retbuf), 0);
	c->retbuf[0] = '\0';
}


void sock_read(aeEventLoop *el, int fd, void *privdata, int mask){
	int len, nleft, cmdret, brk;
	char buf[CMDBUF_MAX];
	caffClient *c = privdata;
	while(1){
        len = read(fd, buf, CMDBUF_MAX);
        if(len == -1){
            if(errno == EAGAIN){
                return;
            }   
            clientFree(c);
            return;
        }else if(len == 0){ 
            clientFree(c);
            return;
        }

		buf[len] = '\0';
		while(1){
            nleft = CMDBUF_MAX - strlen(c->cmdbuf) - 1;
            if(nleft > len){
                strcat(c->cmdbuf, buf);
				buf[0] = '\0';
                brk = 1;
            }else{
                strncat(c->cmdbuf, buf, nleft);
				strcpy(buf, buf+nleft);
                brk = 0;
            }
			while(1){
				cmdret = runCommand(c);
				if(!cmdret)break;
			}
			if(brk)
			{
				break;
			}
		}

        if(len < CMDBUF_MAX - 1){
            break;
        }
	}
}

int usage(void){
	printf("Caff Version %s:\n", CAFF_VERSION);
	printf("options:\n");
	printf("  -h            show this\n");
	printf("  -d            run caff as a daemon\n");
	printf("  -p port       set the port to listen[default:8964]\n");
	printf("  -P pidpath    set the pid file's path\n");
	printf("  -f uuidpath	set the uuid file's path\n");
	printf("  -k uuidkey	the key which memcached client used\n");
	exit(1);
}

int initConf(int argc, char **argv){
	server.port = 8964;
	server.pidfile = NULL;
	server.daemonize = false;
	server.key = "uuid";
	server.uuidpath = "/tmp/uuid.caff";

	int c;
	while(-1 != (c = getopt(argc, argv, "hdp:P:f:k:"))){
		switch(c){
			case 'd':
				server.daemonize = true;
				break;
			case 'p':
				server.port = atoi(optarg);
				break;
			case 'P':
				server.pidfile = optarg;
				break;
			case 'f':
				server.uuidpath = optarg;
				break;
			case 'k':
				server.key = optarg;
				break;
			default:
				return FAIL;
				break;
		}
	}

	return SUCCESS;
}

void daemonize(){
    int fd; 
    if(fork() != 0)exit(0);
    setsid();
    if((fd = open("/dev/null", O_RDWR, 0)) != -1){
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        if(fd > STDERR_FILENO)close(fd);
    } 
}

void savepid(const pid_t pid, const char *pid_file) {
    FILE *fp; 
    if (server.pidfile == NULL)
        return;

    if ((fp = fopen(pid_file, "w")) == NULL) {
        return;
    }

    fprintf(fp,"%ld\n", (long)pid);
    if (fclose(fp) == -1) {
        return;
    }    
}


caffClient *clientCreate(int fd){
	setnonblock(fd);

	caffClient *c = (caffClient *)malloc(sizeof(caffClient));
	bzero(c, sizeof(caffClient));

	c->fd = fd;

	c->cmdbuf[0] = '\0';
	c->retbuf[0] = '\0';

	server.clients[fd] = c;
	if(aeCreateFileEvent(server.el,fd,AE_READABLE,sock_read, c) == AE_ERR)
	{
		clientFree(c);
		exit(1);
	}
	return c;
}

void clientFree(caffClient *c){
	close(c->fd);
	aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
	aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
	server.clients[c->fd] = NULL;
	free(c);
}

void setnonblock(int fd){
 	/* set nonblock */
	int flags;
	flags = fcntl(fd,F_GETFL,0);
	fcntl(fd,F_SETFL,flags|O_NONBLOCK);
}

int tokenize(char *str, char **tokens, char token, int maxtoken){
	char *s, *e;
    int ntoken = 0;
	s = e = str;
	while(maxtoken){
		if(*e == token){
			tokens[ntoken] = s;
			*e = '\0';
			e++;
			s = e;
			ntoken++;
			maxtoken--;
			continue;
		}else if(*e == '\0'){
			tokens[ntoken] = s;
			break;
		}
		e++;
	}
	ntoken++;
	return ntoken;
}

int runCommand(caffClient *c){
	if(strlen(c->cmdbuf) <= 4){
		return 0;
	}
	int nline = getlinenum(c->cmdbuf);

	if(strncmp(c->cmdbuf, "get", 3) == 0){
		if(nline < 2)return 0;
		char line[LINELEN_MAX];
		getline(c->cmdbuf, line);
		int ntoken;
		char *tokens[CMD_TOKEN_MAX];
		ntoken = tokenize(line, tokens, CMD_TOKEN, CMD_TOKEN_MAX);
		if((ntoken != 2 && (ntoken == 3 && *tokens[2] != '\0')) || strncmp(tokens[1], server.key, strlen(server.key)) != 0){
		//if(ntoken != 2 || strncmp(tokens[1], server.key, strlen(server.key)) != 0){
			c->cmdbuf[0] = '\0';
			sendCli(c, "ERROR\r\n");
			return 0;
		}
		uuid(c);
		return 1;
	}else if(strncmp(c->cmdbuf, "quit", 4) == 0){
		clientFree(c);
	}else if(strncmp(c->cmdbuf, "set", 3) == 0){
		if(nline < 3)return 0;
		char line[LINELEN_MAX];
		getline(c->cmdbuf, line);
		int ntoken;
		char *tokens[CMD_TOKEN_MAX];
		ntoken = tokenize(line, tokens, CMD_TOKEN, CMD_TOKEN_MAX);
		if(ntoken != 5 || strncmp(tokens[1], server.key, strlen(server.key)) != 0){
			return 0;
		}

		char initstr[LINELEN_MAX];
		getline(c->cmdbuf, initstr);
		uuidtype initnum = strtoul(initstr, 0, 0);
		getline(c->cmdbuf, NULL);
		inituuid(c, initnum);
		return 1;
	}else{
		c->cmdbuf[0] = '\0';
		sendCli(c, "ERROR\r\n");
	}

	return 0;
}

int getlinenum(char *str){
	char *s, *e;
	int linenum = 1;
	s = e = str;
	while(1){
		if(*e == '\r' && *(e+1) == '\n'){
			e += 2;
			linenum++;
			continue;
		}else if(*e == '\0'){
			break;
		}
		e++;
	}
	return linenum;
}

void getline(char *str, char *line){
	char *s, *e;
	s = e = str;
	while(1){
		if(*e == '\r' && *(e+1) == '\n'){
			*e = '\0';
			e += 2;
			break;
		}else if(*e == '\0'){
			break;
		}
		e++;
	}
	if(line != NULL){
		if(strlen(s) >= LINELEN_MAX){
			strncpy(line, s, LINELEN_MAX);
		}else{
			strcpy(line, s);
		}
	}
	strcpy(str, e);
}

void inituuid(caffClient *c, uuidtype start){
	int fd, i;
	fd = open(server.uuidpath, O_CREAT|O_RDWR, 0777);
	int skeylen = strlen(server.key);
	int size = FHEADLEN + UUIDLEN + skeylen + 2;
	write(fd, "", size);
	void *map = mmap(NULL, UUIDLEN, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
	strcpy(map, FHEAD);
	strcat(map, server.key);
	strcat(map, "\r\n");
	server.uuidnum = map + FHEADLEN + skeylen + 2;
	close(fd);
	*(server.uuidnum) = start;
	char *ret;
	ret = "STORED\r\n";
	sendCli(c, ret);
}

/* the commands exec function */
void uuid(caffClient *c){
	char ret[50];
	ret[0] = '\0';
	uuidtype uuidnum;
	if(server.uuidnum == NULL){
		uuidnum = 0;
	}else{
		(*(server.uuidnum))++;
		uuidnum = *(server.uuidnum);
	}
	char num[30];
	num[0] = '\0';
	sprintf(num, "%lu", uuidnum);
	int numlen = strlen(num);
	sprintf(ret, "VALUE uuid 0 %d\r\n%s\r\n%s", numlen, num, CMD_BACK_END);
	sendCli(c, ret);
}

void sendCli(caffClient *c, char *msg){
	if(strlen(msg) + strlen(c->retbuf) >= RETBUF_MAX)
	{
		return;
	}

	strcat(c->retbuf, msg); 

    if (c->fd <= 0) return;
    if (strlen(c->retbuf) == 0 
	|| aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,sock_write, c) == AE_ERR)
	{
		return;
	}

}

void trim(char *c){
	int len = strlen(c);
	while(len--){
		if(c[len] == '\n' || c[len] == '\r'){
			c[len] = '\0';
		}else{
			break;
		}
	}
}

void sighandler(int signum){
	close(server.fd);
	exit(0);
}

int main(int argc, char **argv){
    bzero(&server, sizeof(server));
	if(initConf(argc, argv) != SUCCESS){
		usage();
	}
	/* when socket error dont close the server */
	signal(SIGPIPE, SIG_IGN);
	signal(SIGHUP, SIG_IGN);
	signal(SIGINT, sighandler);

	if(server.daemonize){
		daemonize();
		savepid(getpid(), server.pidfile);
	}
	initServer();
	return 0;
}
